/**
 * 余额模型。
 */
const rs = require('randomstring');
const models = require('../../utils/models');
const constants = require('../../utils/constants');
const crypto = require('crypto');
const SaltLength = 32;

module.exports = class BalanceModel extends think.Model {
    get tableName() {
        return 'user_balance';
    }

    get relation() {
        return {
            user: {
                type: think.Model.BELONG_TO,
                key: 'id',
                fKey: 'id'
            }
        };
    }

    /**
     * 获取余额。其中会测试是否正常，如果异常，不能再使用。如果不存在，先创建一条。
     */
    async getBalance(userId) {
        const rec = await this.where({id: userId}).find();
        if (think.isEmpty(rec)){
            const salt = rs.generate(SaltLength);
            const checksum = BalanceModel.calcChecksum(0, salt);
            await this.add({
                id: userId,
                balance: 0,
                salt: salt,
                checksum: checksum,
                status: 0
            });

            return { pass: true, balance: 0 };
        }

        const valid = BalanceModel.checkChecksum(rec.balance, rec.salt, rec.checksum);
        if (!valid || rec.balance < 0) {
            await this.where({id: userId}).update({status: 1, notes: '余额校验出现异常'});
            return { pass: false, balance: 0 };
        }

        return { pass: true, balance: rec.balance };
    }

    /**
     * 更新余额。并增加余额变更记录。需要校验用户当前余额是否为负。replay攻击会导致多次请求。
     *
     * @param userId
     * @param amount 变更金额，为负表示减少
     * @param reason
     *
     * @return pass字段为false，表示校验失败或余额出现异常。
     */
    async updateBalance(userId, amount, reason) {
        amount = parseInt(amount) || 0;
        const balanceRes = await this.getBalance(userId);

        if (!balanceRes.pass) {
            return balanceRes;
        } else {
            const rec = await this.where({id: userId}).find();
            if (amount < 0 && (rec.balance + amount) < 0) return  {pass : false}; // replay攻击时，balance值会小于0

            const newBalance = rec.balance + amount;
            const newChecksum = BalanceModel.calcChecksum(newBalance, rec.salt);
            await this.where({id: userId}).update({
                balance: newBalance,
                checksum: newChecksum
            });

            const reasonDesc = BalanceModel.translateReason(reason, amount);
            const bhModel = think.model(models.UserBalanceHistory).db(this.db());
            await bhModel.createOne(userId, rec.balance, amount, reason, reasonDesc);

            // 返回新的余额
            return {pass: true, balance: newBalance};
        }
    }

    /**
     * 检查校验值是否正常.
     */
    static checkChecksum(balance, salt, checksum) {
        const res = BalanceModel.calcChecksum(balance, salt);
        return res === checksum;
    }

    /**
     * 计算checksum。
     *
     * 加盐算法：生成一个SaltLength长度的随机串，然后在中间位置插入余额，再求值。
     */
    static calcChecksum(balance, salt) {
        if (salt.length !== SaltLength) return false;

        const vi = salt.substring(0, SaltLength / 2) + balance + salt.substring(SaltLength / 2);
        return crypto.createHash('sha256').update(vi).digest('base64');
    }

    /**
     * 翻译余额变更原因。
     */
    static translateReason(reason, amount) {
        // 增加余额变更记录
        let reasonDesc = null;
        switch(reason) {
            case constants.BalanceChangeType.Withdraw:
                reasonDesc = '[支出]提现(含手续费)';
                break;
            case constants.BalanceChangeType.Commision:
                reasonDesc = '[收入]渠道佣金';
                break;
            case constants.BalanceChangeType.SendPacket:
                reasonDesc = '[支出]发福袋';
                break;
            case constants.BalanceChangeType.ReceivePacket:
                reasonDesc = '[收入]抢福袋';
                break;
            case constants.BalanceChangeType.Recharge:
                reasonDesc = '[收入]充值';
                break;
            case constants.BalanceChangeType.Reward:
                reasonDesc = amount > 0 ? '[收入]打赏' : '[支出]打赏';
                break;
            default:
                reasonDesc = amount > 0 ?  '[收入]' : '[支出]';
                break;
        }

        return reasonDesc;
    }
};
